/**
 * @file Define base application class
 * 
 * @author Steve Gao<gaofu@parllay.com>
 */
const tty               = require('tty');
const EventEmitter      = require('events');

const ApplicationContext  = require('../application-context').ApplicationContext;
const {Configuration, ApplicationEvents, SystemServices}       = require('../constants');


const ApplicationEventMap = {
    "terminating" : {
        name: "terminating",
        "description" : "Fire when application receive ternimate signal."
    },
    "maintaining" : {
        "name" : "maintaining",
        "description" : "Fire when when administrator put the application into maintaining mode."
    },
    "hangup" : {
        "name" : "hangup",
        "description" : "Fire when administrator put the application the hangup mode."
    },
    "resume" : {
        "name" : "resume",
        "description" : "Fire when administrator resume the application"
    },
    "exception" : {
        "name" : "exception",
        "description" : "Fire when the application meet uncaught exception."
    },
    "service_error" : {
        "name" : "service_error",
        "description": "Fire when our service got error"
    },
    "web_request_error" : {
        "name" : "web_request_error",
        "description" : "Fire when web request got error"
    }
};

/**
 * The parent class for all Application classes
 * 
 * @class Application
 * @extends {EventEmitter}
 */
class Application extends EventEmitter {

    get logger() {
        if ( !this._logger ) {
            var logService = this.Context.getSystemService(SystemServices.LOG_SERVICE);
            this._logger = logService ? logService.getLogger() : console;
        }

        return this._logger;
    }

    constructor() {
        //NOTICE: If you extends the EventEmitter, you must callback super, otherwise, the context keywork this will be not initialized
        super(); 
        this.setupApplication();
    }

    get EventList() {
        if (!this._ApplicationEvents) {
            this._ApplicationEvents = ApplicationEventMap;
        }
        return this._ApplicationEvents;
    }

    /**
     * Define customzation event
     * 
     * @name defineApplicationEvent
     * @param {String} eventName - The user-defined event name
     * @param {String} 
     * 
     * @return {Boolean}
     * @memberOf Application
     */
    defineApplicationEvent(eventName, description) {
        if (this.EventList[eventName]) {
            throw new Error(`We already define the event[${eventName}]!`);
        }

        this.EventList[eventName] = {
            name: eventName,
            description: description
        };
        
        return true;
    }

    get name() {
        return "Application";
    }

    get Context() {
        if (!this._context) {
            this._context = new ApplicationContext(this, process.cwd());
        }
        return this._context
    }

    get Configuration() {
        return null;
    }

    get IsUnderMaintainancing() {
        return false;
    }

    /**
     * Setup the application context
     * 
     * @name setupApplicationContext
     * 
     * @returns {void}
     */
    setupApplicationContext() {}

    /**
     * Setup the application
     * 
     * @memberOf Application
     */
    setupApplication() {
        this.registerTerminationHandlers();
    }

    /**
     * Register event handler for current application
     * 
     * @name addEventListener
     * @name {String} eventName - The event name of supported by appliation
     * @name {Function} handler - The event handler when the event fire
     * 
     * @memberOf Application
     */
    
    addEventListener(eventName, handler) {
        if (!this.EventList[eventName]) {
            throw new Error(`Not supported event: $(eventName)!`);
        }

        if (typeof handler !== 'function') {
            throw new Error("The parameter 'handler' is not a function!");
        }

        this.on(eventName, handler);
    }

    /**
     * Register process termination handlers
     * 
     * @name registerTerminationHandlers
     * 
     * @returns {void}
     */
    registerTerminationHandlers() {
        var me = this;

        [
            "SIGTERM", 
            "SIGINT", 
            "SIGBREAK",
            "SIGQUIT"
        ].forEach(function(signal) {
            me.registerTerminationEventHandler(signal);
        });

        process.once('exit', function() {

        });

        this.registerAdministrationEventHandler();
        this.registerUncatchExceptionHandler();
    }

    /**
     * Register process termination event handlers
     * 
     * @param {String} eventName - The event name
     * @returns
     * 
     * @memberOf Application
     */
    registerTerminationEventHandler(signal) {
        var me = this;
        process.once(signal, () => {
            if (me.hasEventListeners(ApplicationEvents.TERMINATING)) {
                me.emit(ApplicationEvents.TERMINATING);
            } else {
                me.quit();
            }
        });
    }

    registerAdministrationEventHandler() {
        var me = this;
        process.once('SIGHUP', () => {
            if (me.hasEventListeners(ApplicationEvents.MAINTAINING)) {
                me.emit(ApplicationEvents.MAINTAINING);
            }
        });

        process.once('SIGCONT', () => {
            if (me.hasEventListeners(ApplicationEvents.RESUME)) {
                me.emit(ApplicationEvents.RESUME);
            }
        });
    }
    
    /**
     * Register the Uncaught Exception handler
     * 
     * @name registerUncatchExceptionHandler
     * @returns
     * 
     * @memberOf Application
     */
    registerUncatchExceptionHandler() {
        var app = this;
        process.once('uncaughtException', (error) => {
            if (app.hasEventListeners(ApplicationEvents.UNCAUGHT_EXCEPTION)) {
                app.emit(ApplicationEvents.UNCAUGHT_EXCEPTION, error);
            } else {
                app.logger && app.logger.error("Uncaught exception happened: ", error);
                app.quit();
            }
        });
    }

    /**
     * Check if there are event handlers for specify event
     * 
     * @name hasEventListeners
     * @param eventName
     * 
     * @return {Boolean}
     * @memberOf Application
     */
    hasEventListeners(eventName) {
        var listeners = this.listeners(eventName);
        
        return (listeners.length > 0);
    }    

    reportError(errorName, error) {
        if ( this.hasEventListeners(errorName) ) {
            this.emit(errorName, error);
        } else {
            this.logger.error("Unknow error happend: ", error);
        }
    }
    /**
     * The abstract method to run the application
     */
    run() {}

    /**
     * The abstract method to quit the appliation
     */
    quit() {
        process.exit(0);
    }
}

exports.Application = Application;